package com.yeswater.redislock.lock;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * @Description: 手写 redis 分布式锁
 * @author： yeswater
 * @create： 2024/6/30
 */

public class RedisDistributedLock implements Lock {
	
	@Resource
	private StringRedisTemplate stringRedisTemplate;
	
	private String lockName;
	
	private String uuidValue;
	
	private long expireTime;
	
	
	public RedisDistributedLock(StringRedisTemplate stringRedisTemplate, String lockName, String uuidValue) {
		this.stringRedisTemplate = stringRedisTemplate;
		this.lockName = lockName;
		this.uuidValue = uuidValue + ":" + Thread.currentThread().getId();
		this.expireTime = 30L;
	}
	
	public RedisDistributedLock(StringRedisTemplate stringRedisTemplate, String lockName, String uuidValue, long expireTime) {
		this.stringRedisTemplate = stringRedisTemplate;
		this.lockName = lockName;
		this.uuidValue = uuidValue + ":" + Thread.currentThread().getId();
		this.expireTime = expireTime;
	}
	
	@Override
	public void lock() {
		tryLock();
	}
	
	@Override
	public boolean tryLock() {
		try {
			tryLock(-1L, TimeUnit.SECONDS);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return false;
	}
	
	/**
	 * 加锁的具体实现
	 */
	@Override
	public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
		if (time != -1L) {
			this.expireTime = unit.toSeconds(time);
		}
		String script = "if redis.call('exists',KEYS[1]) == 0 " +
				                "or redis.call('hexists',KEYS[1],ARGV[1]) == 1 " +
				                "then redis.call('hincrby',KEYS[1],ARGV[1],1) " +
				                "redis.call('expire',KEYS[1],ARGV[2]) " +
				                "return 1 else return 0 " +
				                "end";
		
		System.out.println("script: " + script);
		System.out.println("lockName: " + lockName);
		System.out.println("uuidValue: " + uuidValue);
		System.out.println("expireTime: " + expireTime);
		
		while (!stringRedisTemplate.execute(RedisScript.of(script, Boolean.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime))) {
			TimeUnit.MILLISECONDS.sleep(50);
		}
		this.renewExpire();
		return true;
	}
	
	/**
	 * 解锁的具体实现
	 */
	@Override
	public void unlock() {
		String script =
				"if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 0 then " +
						"   return nil " +
						"elseif redis.call('HINCRBY',KEYS[1],ARGV[1],-1) == 0 then " +
						"   return redis.call('del',KEYS[1]) " +
						"else " +
						"   return 0 " +
						"end";
		// nil = false 1 = true 0 = false
		System.out.println("lockName: " + lockName);
		System.out.println("uuidValue: " + uuidValue);
		System.out.println("expireTime: " + expireTime);
		Long flag = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime));
		if (flag == null) {
			throw new RuntimeException("This lock doesn't EXIST");
		}
	}
	
	private void renewExpire() {
		String script =
				"if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 1 then " +
						"return redis.call('expire',KEYS[1],ARGV[2]) " +
						"else " +
						"return 0 " +
						"end";
		
		new Timer().schedule(new TimerTask() {
			@Override
			public void run() {
				if (stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime))) {
					renewExpire();
				}
			}
		}, (this.expireTime * 1000) / 3);
	}
	
	@Override
	public void lockInterruptibly() throws InterruptedException {
		
	}
	
	@Override
	public Condition newCondition() {
		return null;
	}
}
